An
example of task parallelism is an image processing application where
images are created with layers. Separate images from different sources
are processed independently and then combined with a process known as alpha blending. This process superimposes semitransparent layers to form a single image.
The source images that are
combined are different, and different image processing operations are
performed on each of them. This means that the image processing
operations must be performed separately on each source image and must
be complete before the images can be blended. In the example, there are
only two source images, and the operations are simple: conversion to
gray scale and rotation. In a more realistic example, there might be
more source images and more complicated operations.
Here’s the sequential code.
static int SeqentialImageProcessing(Bitmap source1, Bitmap source2,
Bitmap layer1, Bitmap layer2,
Graphics blender)
{
SetToGray(source1, layer1);
Rotate(source2, layer2);
Blend(layer1, layer2, blender);
return source1.Width;
}
In this example, source1 and source2 are bitmaps that are the original source images, layer1 and layer2 are bitmaps that have been prepared with additional information needed to blend the images, and blender is a Graphics instance that performs the blending and references the bitmap with the final blended image. Internally, SetToGray, Rotate, and Blend use methods from the .NET System.Drawing
namespace to perform the image processing. The last statement returns
an integer that the caller uses to print a progress message.
The SetToGray and Rotate
methods are entirely independent of each other. This means that you can
execute them in separate tasks. If two or more cores are available, the
tasks might run in parallel, and the image processing operations might complete in less elapsed time than a sequential version would.
The parallel code uses tasks explicitly.
static int ParallelTaskImageProcessing(Bitmap source1,
Bitmap source2, Bitmap layer1, Bitmap layer2,
Graphics blender)
{
Task toGray = Task.Factory.StartNew(() =>
SetToGray(source1, layer1));
Task rotate = Task.Factory.StartNew(() =>
Rotate(source2, layer2));
Task.WaitAll(toGray, rotate);
Blend(layer1, layer2, blender);
return source1.Width;
}
This code calls Task.Factory.StartNew to create and run two tasks that execute SetToGray and Rotate, and calls Task.WaitAll to wait for both tasks to complete before blending the processed images.
You can also use the Parallel.Invoke method to achieve parallelism. The Parallel.Invoke method has very convenient syntax. This is shown in the following code.
static int ParallelInvokeImageProcessing(Bitmap source1,
Bitmap source2, Bitmap layer1, Bitmap layer2,
Graphics blender)
{
Parallel.Invoke(
() => SetToGray(source1, layer1),
() => Rotate(source2, layer2));
Blend(layer1, layer2, blender);
return source1.Width;
}
Here the tasks are identified implicitly by the arguments to Parallel. Invoke. This call does not return until all of the tasks complete.